package com.lingchou.cloud.process.client.controller;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.lc.common.bean.response.Result;
import com.lc.common.exception.HandlerException;
import com.lingchou.cloud.process.api.controller.ProcessDeployApi;
import com.lingchou.cloud.process.api.dto.param.DeployParamVo;
import com.lingchou.cloud.process.api.dto.param.ProcessNode;
import com.lingchou.cloud.process.api.dto.result.ProcessDefinitionDTO;
import com.lingchou.cloud.process.api.dto.result.ProcessInstanceDTO;
import com.lingchou.cloud.process.api.enums.CommentTypeEnum;
import com.lingchou.cloud.process.client.dao.DefinitionSupplementaryDao;
import com.lingchou.cloud.process.client.entity.DefinitionSupplementary;
import com.lingchou.cloud.process.client.service.ProcessModelService;
import com.lingchou.cloud.process.client.service.ProcessUtilService;
import com.lingchou.cloud.process.client.util.ThreadLocalUtil;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.validation.ProcessValidator;
import org.flowable.validation.ProcessValidatorFactory;
import org.flowable.validation.ValidationError;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@Validated
public class ProcessDeployApiImpl implements ProcessDeployApi {
    private final RepositoryService repositoryService;
    private final ProcessUtilService processUtilService;
    private final ProcessModelService processModelService;
    private final IdentityService identityService;
    private final RuntimeService runtimeService;
    private final DefinitionSupplementaryDao definitionSupplementaryDao;

    public ProcessDeployApiImpl(RepositoryService repositoryService, ProcessUtilService processUtilService, ProcessModelService processModelService, IdentityService identityService, RuntimeService runtimeService, DefinitionSupplementaryDao definitionSupplementaryDao) {
        this.repositoryService = repositoryService;
        this.processUtilService = processUtilService;
        this.processModelService = processModelService;
        this.identityService = identityService;
        this.runtimeService = runtimeService;
        this.definitionSupplementaryDao = definitionSupplementaryDao;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<ProcessInstanceDTO> deployByModelAndStart(DeployParamVo deployParamVo) {
        ProcessInstanceDTO processInstanceDTO = deployProcessByModelAndStart(deployParamVo);
        return processInstanceDTO == null ? Result.failure("流程启动失败") : Result.success(processInstanceDTO);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result<Map<String, ProcessInstanceDTO>> deployByModelAndStartBatch(List<DeployParamVo> deployParamVos) {
        List<ProcessInstanceDTO> processInstanceDTOS = new ArrayList<>();
        deployParamVos.forEach(deployParamVo -> processInstanceDTOS.add(deployProcessByModelAndStart(deployParamVo)));
        if (!processInstanceDTOS.isEmpty()) {
            Map<String, ProcessInstanceDTO> map = new HashMap<>(16);
            processInstanceDTOS.forEach(processInstanceDTO -> map.put(processInstanceDTO.getName(), processInstanceDTO));
            return Result.success(map);
        }
        return Result.success();
    }

    /**
     * 发布并启动流程
     *
     * @param deployParamVo 发布参数
     * @return 流程实例
     */
    @Transactional(rollbackFor = Exception.class)
    public ProcessInstanceDTO deployProcessByModelAndStart(DeployParamVo deployParamVo) {
        //启动变量 针对会签节点的实例的人的集合
        Map<String, Object> variables = new HashMap<>(16);
        //声明BpmnModel
        BpmnModel bpmnModel = new BpmnModel();
        //声明Process
        Process process = new Process();
        process.setId("ID" + IdUtil.simpleUUID());
        //开始事件
        StartEvent startEvent = processModelService.createStartEvent(process, "ID" + IdUtil.simpleUUID(), "start");
        //结束事件
        EndEvent endEvent = processModelService.createEndEvent(process, "ID" + IdUtil.simpleUUID(), "end");
        //定时服务任务
        ServiceTask timeServiceTask = processModelService.createServiceTask(process, "ID" + IdUtil.simpleUUID(), "timeServiceTask");
        //连线定时任务与end节点
        processModelService.createSequenceFlow(process, timeServiceTask.getId(), endEvent.getId(), "ID" + IdUtil.simpleUUID(), "N" + IdUtil.simpleUUID(), StrUtil.EMPTY);
        //当前节点
        ProcessNode currNode;
        //最后一个节点ID 初始是开始节点的id
        String lastElementId = startEvent.getId();
        //配置变量
        List<DefinitionSupplementary> varList = new ArrayList<>();
        //遍历生成
        for (int i = 0; i < deployParamVo.getProcessNodes().size(); i++) {
            //赋值curr为头节点
            currNode = deployParamVo.getProcessNodes().get(i);
            //初始curr的task的id
            String currTaskId = "ID" + IdUtil.simpleUUID();
            //上一节点的出口线
            String lastLineId = "ID" + IdUtil.simpleUUID();
            processModelService.createSequenceFlow(process, lastElementId,
                    currTaskId, lastLineId, "N" + lastLineId, StrUtil.EMPTY);
            //人员集合变量名
            String collectionString = "P" + IdUtil.simpleUUID();
            switch (currNode.getNodeType()) {
                case SINGLE:
                    processModelService.createAssigneeUserTask(process, currTaskId, currNode.getNodeName(), currNode.getUserIds().get(0), currNode.getEndTime().toString(), timeServiceTask.getId());
                    //塞入循环的变量
                    variables.put(collectionString, currNode.getUserIds().get(0));
                    break;
                case WITH_SIGN:
                    processModelService.createUserTaskWithSign(process, currTaskId, currNode.getNodeName(), collectionString, currNode.getEndTime().toString(), timeServiceTask.getId());
                    //塞入循环的变量
                    variables.put(collectionString, currNode.getUserIds());
                    break;
                case OR_SIGN:
                    processModelService.createUserTaskOrSign(process, currTaskId, currNode.getNodeName(), collectionString, currNode.getEndTime().toString(), timeServiceTask.getId());
                    //塞入循环的变量
                    variables.put(collectionString, currNode.getUserIds());
                    break;
                default:
                    throw new HandlerException("流程定义节点判断异常");
            }
            //赋值最后一个Task
            lastElementId = currTaskId;
            //添加变量
            DefinitionSupplementary definitionSupplementary = new DefinitionSupplementary();
            definitionSupplementary.setId(IdUtil.simpleUUID());
            definitionSupplementary.setTaskDefId(currTaskId);
            definitionSupplementary.setSelectionRange(currNode.getSelectionRange());
            definitionSupplementary.setPersonCollParamName(collectionString);
            definitionSupplementary.setDueTimeParamName(currNode.getEndTime().toString());
            String nodeTypeParamName = "P" + IdUtil.simpleUUID();
            variables.put(nodeTypeParamName, currNode.getNodeType().name());
            definitionSupplementary.setNodeTypeParamName(nodeTypeParamName);
            definitionSupplementary.setCreateTime(LocalDateTime.now());
            definitionSupplementary.setUpdateTime(LocalDateTime.now());
            varList.add(definitionSupplementary);
        }
        //结束线
        processModelService.createSequenceFlow(process, lastElementId,
                endEvent.getId(), "ID" + IdUtil.simpleUUID(), "endLine", StrUtil.EMPTY);
        //流程添加
        bpmnModel.addProcess(process);
        ProcessValidatorFactory processValidatorFactory = new ProcessValidatorFactory();
        ProcessValidator defaultProcessValidator = processValidatorFactory.createDefaultProcessValidator();
        // 验证失败信息的封装ValidationError
        List<ValidationError> validate = defaultProcessValidator.validate(bpmnModel);
        if (!validate.isEmpty()) {
            String errorMessage = validate.stream().map(validationError -> validationError.getActivityName() + ":" + validationError.getProblem()).collect(Collectors.joining(";"));
            throw new HandlerException(errorMessage);
        }
        //流程发布
        Deployment deployment = repositoryService
                .createDeployment()
                .addBpmnModel((StrUtil.isBlank(deployParamVo.getProcessName()) ? "ID" + IdUtil.simpleUUID() : deployParamVo.getProcessName()) + ".bpmn", bpmnModel)
                .key("K" + IdUtil.simpleUUID())
                .deploy();
        //查询流程定义
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deployment.getId()).latestVersion().singleResult();
        identityService.setAuthenticatedUserId(deployParamVo.getOperator());
        //变量存储
        varList.forEach(definitionSupplementary -> definitionSupplementary.setProcDefId(processDefinition.getId()));
        definitionSupplementaryDao.saveAll(varList);
        //流程启动
        ProcessInstance processInstance = runtimeService.startProcessInstanceById(processDefinition.getId(), deployParamVo.getBusinessKey(), variables);
        ProcessInstanceDTO processInstanceDTO = processUtilService.buildProcessInstance(processInstance);
        processInstanceDTO.setName(StrUtil.isBlank(deployParamVo.getProcessName()) ? "ID" + IdUtil.simpleUUID() : deployParamVo.getProcessName());
        //流程赋值NAME
        runtimeService.setProcessInstanceName(processInstance.getId(),
                processInstanceDTO.getName());
        //塞入ThreadLocal
        ThreadLocalUtil.putStatus(CommentTypeEnum.IN_REVIEW);
        return processInstanceDTO;
    }


    @Override
    public Result<ProcessDefinitionDTO> deployByXmlPath(String xmlPath) {
        //部署流程
        Deployment deployment = repositoryService.createDeployment()
                .name("报销流程")
//                .addClasspathResource("flowable/diagram.bpmn20.xml")
                .addClasspathResource("flowable/diagram.bpmn")
                .deploy();
        return processUtilService.packageProcessDefinitionByDeploymentId(deployment.getId(), "流程定义失败");
    }

    @Override
    public Result<ProcessDefinitionDTO> deployByXmlString(String xmlString) {
        //部署流程
        Deployment deployment = repositoryService.createDeployment()
                .name("报销流程")
                .addString("ID" + IdUtil.simpleUUID() + ".bpmn", xmlString)
                .deploy();
        return processUtilService.packageProcessDefinitionByDeploymentId(deployment.getId(), "流程定义失败");
    }

    @Override
    public Result<Object> getElements(String defId) {
        Process process = repositoryService.getBpmnModel(defId).getMainProcess();
        List<UserTask> elements = process.findFlowElementsOfType(UserTask.class);
        return Result.success(elements);
    }

}
